package q5_blocking_queue_test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/*
ArrayBlockingQueue：一个基于数组结构的有界阻塞队列。此队列按 FIFO（先进先出）原则对元素进行排序。
LinkedBlockingQueue：一个基于链表结构的阻塞队列。此队列按 FIFO（先进先出）原则对元素进行排序，吞吐量通常要高于ArrayBlockingQueue。
SynchronousQueue：一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作，否则插入操作一直处于阻塞状态，吞吐量通常要高于LinkedBlockingQueue。

PriorityBlockingQueue:支持优先级排序的无界阻塞队列.
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列.
LinkedTransferQueue:由链表结构组成的无界阻塞队列.
LinkedBlockingDeque:由了解结构组成的双向阻塞队列.

1 队列

2 阻塞队列
    阻塞队列,顾名思义,首先它是一个队列
    当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
    当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.
    同样
    试图往已满的阻塞队列中添加新圆度的线程同样也会被阻塞,知道其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增.
    2.1 阻塞队列有没有好的一面
        在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒
        为什么需要使用BlockingQueue
        好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了
        在concurrent包 发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度.
    2.2 不得不阻塞，你如何管理
        当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
        当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.
    2.3 用在哪里
        2.3.1 生产者消费者模式
        2.3.2 线程池
        2.3.3 消息中间件
*/
public class BlockingQueueDemo {

    public static void main(String[] args) {
//        apiDemo1();
//        apiDemo2();
//        try {
//            apiDemo3();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        try {
//            apiDemo4();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        synchronousQueueDemo();
    }

    public static void apiDemo1(){
        /*
        抛出异常
        当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
        当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
        */
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //java.lang.IllegalStateException: Queue full
        //System.out.println(blockingQueue.add("c"));

        //取出队列头元素
        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //java.util.NoSuchElementException
        //System.out.println(blockingQueue.remove());\
    }

    public static void apiDemo2(){
        /*
        特殊值
        插入方法,成功返回true 失败返回false
        移除方法,成功返回元素,队列里面没有就返回null
         */
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //不抛异常，只是返回false，表示插入失败
        //System.out.println(blockingQueue.offer("c"));

        System.out.println(blockingQueue.peek());

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //不抛异常，直接返回null
        System.out.println(blockingQueue.poll());
    }

    public static void apiDemo3() throws InterruptedException {
        /*
        一直阻塞（多用于类似生产者/消费者模式的消息队列）
        当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出
        当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
         */
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //队列满了，再往队列中塞元素，则一直阻塞等待，只有当队列其中一个元素被take()后，才会插入并停止阻塞
        //blockingQueue.put("a");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //队列空了，再从队列中取元素，则一直阻塞等待，只有当队列中被put进元素后，才会取出并停止阻塞
        //System.out.println(blockingQueue.take());
    }

    public static void apiDemo4() throws InterruptedException {
        /*
        超时退出
        当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出
         */
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b",2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("c",2L, TimeUnit.SECONDS));
        //超时后失败并返回false
        System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));

        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        //超时后失败并返回null
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
    }

    public static void synchronousQueueDemo(){
        /*
        SynchronousQueue没有容量
        与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue
        每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
         */
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AA").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t take " +  blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t take " +  blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t take " +  blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}
